package com.dyj.examples.service;

import com.dyj.common.domain.ClientTokenInfo;
import com.dyj.common.domain.UserTokenInfo;
import com.dyj.common.domain.vo.*;
import com.dyj.common.exception.AuthTokenNotFoundException;
import com.dyj.common.service.IAgentTokenService;
import com.dyj.web.DyWebClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * @author danmo
 * @date 2024-04-12 10:51
 **/
public class RedisAgentTokenService implements IAgentTokenService {

    private final RedisTemplate redisTemplate;

    private final Map<String,Lock> lockMap = new ConcurrentHashMap<>();

    public RedisAgentTokenService(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void setUserTokenInfo(Integer tenantId, String clientKey, String accessToken, Long expiresIn, String refreshToken, Long refreshExpiresIn, String openId) throws AuthTokenNotFoundException {
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        userTokenInfo.setAccessToken(accessToken);
        userTokenInfo.setExpiresIn(expiresIn);
        userTokenInfo.setRefreshToken(refreshToken);
        userTokenInfo.setRefreshExpiresIn(refreshExpiresIn);
        userTokenInfo.setOpenId(openId);
        redisTemplate.opsForValue()
                .set(String.format("userToken:%s_%s_%s", tenantId, clientKey, openId), userTokenInfo, expiresIn, TimeUnit.SECONDS);
        redisTemplate.opsForValue()
                .set(String.format("userRefToken:%s_%s_%s", tenantId, clientKey, openId), userTokenInfo,refreshExpiresIn, TimeUnit.SECONDS);
    }

    @Override
    public UserTokenInfo getUserTokenInfo(Integer tenantId, String clientKey, String openId) throws AuthTokenNotFoundException {
        ValueOperations<String, UserTokenInfo> valueOperations = this.redisTemplate.opsForValue();
        UserTokenInfo userTokenInfo = valueOperations.get(String.format("userToken:%s_%s_%s", tenantId, clientKey, openId));
        if (Objects.isNull(userTokenInfo)) {
            UserTokenInfo refUserTokenInfo = valueOperations.get(String.format("userRefToken:%s_%s_%s", tenantId, clientKey, openId));
            if(Objects.nonNull(refUserTokenInfo)){
                RefreshAccessTokenVo refreshAccessTokenVo = DyWebClient.getInstance().refreshAccessToken(refUserTokenInfo.getOpenId()).getData();
                if(Objects.nonNull(refreshAccessTokenVo) && refreshAccessTokenVo.getError_code()==0){
                    setUserTokenInfo(tenantId, clientKey, refreshAccessTokenVo.getAccess_token(), refreshAccessTokenVo.getExpires_in(), refreshAccessTokenVo.getRefresh_token(), refreshAccessTokenVo.getRefresh_expires_in(), refUserTokenInfo.getOpenId());
                }
            }else {
                throw new AuthTokenNotFoundException("token not found");
            }
        }
        return userTokenInfo;
    }

    @Override
    public ClientTokenInfo setClientTokenInfo(Integer tenantId, String clientKey, String accessToken, Long expiresIn) {
        ClientTokenInfo clientTokenInfo = new ClientTokenInfo();
        clientTokenInfo.setAccessToken(accessToken);
        clientTokenInfo.setExpiresIn(expiresIn);
        redisTemplate.opsForValue().set(String.format("clientToken:%s_%s", tenantId, clientKey), clientTokenInfo, expiresIn, TimeUnit.SECONDS);
        return clientTokenInfo;
    }

    @Override
    public ClientTokenInfo getClientTokenInfo(Integer tenantId, String clientKey) throws AuthTokenNotFoundException {
        ValueOperations<String, ClientTokenInfo> valueOperations = this.redisTemplate.opsForValue();
        return valueOperations.get(String.format("clientToken:%s_%s", tenantId, clientKey));
    }

    @Override
    public ClientTokenInfo computeClientTokenInfoIfAbsent(Integer tenantId, String clientKey, Supplier<ClientTokenInfo> mappingSupplier) throws AuthTokenNotFoundException {
        String key = String.format("clientToken:%s_%s", tenantId, clientKey);
        ValueOperations<String, ClientTokenInfo> valueOperations = this.redisTemplate.opsForValue();
        ClientTokenInfo clientTokenInfo = valueOperations.get(key);
        if (clientTokenInfo != null) {
            return clientTokenInfo;
        }
        Lock lock = lockMap.computeIfAbsent(key +"-clientToken", k -> new ReentrantLock());
        lock.lock();
        try {
            clientTokenInfo = valueOperations.get(key);
            if (clientTokenInfo != null) {
                return clientTokenInfo;
            }
            clientTokenInfo = mappingSupplier.get();
            if (clientTokenInfo == null) {
                throw new AuthTokenNotFoundException("clientToken获取失败");
            }
            redisTemplate.opsForValue().set(key, clientTokenInfo, clientTokenInfo.getExpiresIn(), TimeUnit.SECONDS);
            return clientTokenInfo;
        }finally {
            lock.unlock();
        }
    }

    @Override
    public BizTokenVo getBizToken(Integer tenantId, String clientKey, String openId) {
        return null;
    }

    @Override
    public void setBizToken(Integer tenantId, String clientKey, String bizToken, Long bizExpiresIn, String bizRefreshToken, Long bizRefreshExpiresIn, String openId) {

    }

    @Override
    public void setAppsV2Token(Integer tenantId, String clientKey, String accessToken, Long expiresIn) {

    }

    @Override
    public AppsV2TokenVo getAppsV2Token(Integer tenantId, String clientKey) {
        return null;
    }

    @Override
    public AppsV2TokenVo computeAppsV2TokenIfAbsent(Integer tenantId, String clientKey, Supplier<AppsV2TokenVo> mappingSupplier) throws AuthTokenNotFoundException {
        String key = String.format("appsV2Token:%s_%s", tenantId, clientKey);
        ValueOperations<String, AppsV2TokenVo> valueOperations = this.redisTemplate.opsForValue();
        AppsV2TokenVo appsV2TokenVo = valueOperations.get(key);
        if (appsV2TokenVo != null) {
            return appsV2TokenVo;
        }
        Lock lock = lockMap.computeIfAbsent(key +"-appsV2Token", k -> new ReentrantLock());
        lock.lock();
        try {
            appsV2TokenVo = valueOperations.get(key);
            if (appsV2TokenVo != null) {
                return appsV2TokenVo;
            }
            appsV2TokenVo = mappingSupplier.get();
            if (appsV2TokenVo == null) {
                throw new AuthTokenNotFoundException("appsV2Token获取失败");
            }
            redisTemplate.opsForValue().set(key, appsV2TokenVo, appsV2TokenVo.getExpires_in(), TimeUnit.SECONDS);
            return appsV2TokenVo;
        }finally {
            lock.unlock();
        }
    }

    @Override
    public TpThirdV2TokenVo getTpThirdV2Token(Integer tenantId, String clientKey) {
        return null;
    }

    @Override
    public void setTpThirdV2Token(Integer tenantId, String clientKey, TpThirdV2TokenVo tpThirdV2Token) {

    }

    @Override
    public TpThirdV2TokenVo computeTpThirdV2TokenIfAbsent(Integer tenantId, String clientKey, Supplier<TpThirdV2TokenVo> mappingSupplier) throws AuthTokenNotFoundException {
        String key = String.format("TpThirdV2Token:%s_%s", tenantId, clientKey);
        ValueOperations<String, TpThirdV2TokenVo> valueOperations = this.redisTemplate.opsForValue();
        TpThirdV2TokenVo tpThirdV2TokenVo = valueOperations.get(key);
        if (tpThirdV2TokenVo != null) {
            return tpThirdV2TokenVo;
        }
        Lock lock = lockMap.computeIfAbsent(key +"-TpThirdV2Token", k -> new ReentrantLock());
        lock.lock();
        try {
            tpThirdV2TokenVo = valueOperations.get(key);
            if (tpThirdV2TokenVo != null) {
                return tpThirdV2TokenVo;
            }
            tpThirdV2TokenVo = mappingSupplier.get();
            if (tpThirdV2TokenVo != null) {
                redisTemplate.opsForValue().set(key, tpThirdV2TokenVo, tpThirdV2TokenVo.getExpires_in(), TimeUnit.SECONDS);
            }
            return tpThirdV2TokenVo;
        }finally {
            lock.unlock();
        }
    }

    @Override
    public TpV2AuthTokenVo getTpV2AuthToken(Integer tenantId, String clientKey) {
        return null;
    }

    @Override
    public void setTpV2AuthToken(Integer tenantId, String clientKey, TpV2AuthTokenVo tpV2AuthToken) {

    }

    @Override
    public TpV2AuthTokenVo computeTpV2AuthTokenIfAbsent(Integer tenantId, String clientKey, Supplier<TpV2AuthTokenVo> mappingSupplier) throws AuthTokenNotFoundException {
        String key = String.format("TpV2AuthToken:%s_%s", tenantId, clientKey);
        ValueOperations<String, TpV2AuthTokenVo> valueOperations = this.redisTemplate.opsForValue();
        TpV2AuthTokenVo tpV2AuthTokenVo = valueOperations.get(key);
        if (tpV2AuthTokenVo != null) {
            return tpV2AuthTokenVo;
        }
        Lock lock = lockMap.computeIfAbsent(key +"-TpV2AuthToken", k -> new ReentrantLock());
        lock.lock();
        try {
            tpV2AuthTokenVo = valueOperations.get(key);
            if (tpV2AuthTokenVo != null) {
                return tpV2AuthTokenVo;
            }
            tpV2AuthTokenVo = mappingSupplier.get();
            if (tpV2AuthTokenVo == null) {
                throw new AuthTokenNotFoundException("TpV2AuthToken获取失败");
            }
            redisTemplate.opsForValue().set(key, tpV2AuthTokenVo, tpV2AuthTokenVo.getExpires_in(), TimeUnit.SECONDS);
            return tpV2AuthTokenVo;
        }finally {
            lock.unlock();
        }
    }
}
